package cn.blnp.net.flowable.task.ext.http;

import cn.hutool.core.map.MapUtil;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.*;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <h3>Http任务案例</h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/5/15 10:39
 */
@Slf4j
@SpringBootTest
public class HttpTaskTest {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;
    @Resource
    private HistoryService historyService;
    @Resource
    private ProcessEngineConfiguration processEngineConfiguration;

    /**
     *  <h2>Http任务</h2>
     *  <p>
     *      顾名思义，Http任务是用于发出HTTP请求的任务，它可以发送HTTP请求并接收响应，可以执行GET、POST、PUT和DELETE等不同类型的请求。通过Http任务，
     *  可以实现与其他系统的数据交互、系统集成、调用外部服务等功能，提供了在工作流中与外部系统进行通信的便捷方式。具体定义格式如下所示：
     *  <pre>
     *      {@code
     *          <serviceTask id="httpTask1" name="Http任务" flowable:type="http" />
     *      }
     *  </pre>
     *  Http任务可以通过属性注入的方式配置各种属性，这些属性的值可以使用UEL表达式，并将在流程执行时进行解析。Http任务可配置属性如下所示：
     *  <ul>
     *      <li>
     *          <b>requestMethod：</b>请求方法，值可以为GET、POST、PUT和DELETE
     *      </li>
     *      <li>
     *          <b>requestUrl：</b>请求URL
     *      </li>
     *      <li>
     *          <b>requestHeaders：</b>行分隔的HTTP请求头。例如：
     *          <pre>
     *              {@code
     *                  Content-Type: application/json
     *                  Authorization: Basic aGFRlc3Q=
     *              }
     *          </pre>
     *      </li>
     *      <li>
     *          <b>requestBody：</b>请求体
     *      </li>
     *      <li>
     *          <b>requestTimeout：</b>请求超时时间，单位为毫秒。默认值为0，即不超时
     *      </li>
     *      <li>
     *          <b>disallowRedirects：</b>是否禁用Http重定向。默认为false
     *      </li>
     *      <li>
     *          <b>failStatusCodes：</b>英文逗号分隔的Http状态码，将使请求失败并抛出FlowableException。例如，400,404,500,503；又如，400,5XX 英文逗号分隔的Http状态码，将使任务抛出BpmnError，
     *          并可用错误边界事件捕获。BpmnError的错误码为HTTP+状态码，如404状态码会将错误码设置为HTTP404。仅当disallowRedirects字段设置为true时，3XX状态码才会被抛出。若同一个状态码在handleStatusCodes
     *          及failStatusCodes中都有配置，则handleStatusCodes生效。例如，400,404,500,503；又如，3XX,4XX,5XX
     *      </li>
     *      <li>
     *          <b>ignoreException：</b>是否忽略异常，默认为false。设置为true时异常将被捕获，并存储在名为errorMessage的变量中
     *      </li>
     *      <li>
     *          <b>saveRequestVariables：</b>是否保存请求参数为变量，默认为false。设置为true时会将调用外部HTTP服务时设置的参数存储到变量中，可以在流程中访问和使用这些参数。默认情况下，只会保存与响应相关的变量
     *      </li>
     *      <li>
     *          <b>saveResponseParameters：</b>是否保存调用外部HTTP服务的响应参数为变量，包括HTTP状态码、响应头等。默认为false
     *      </li>
     *      <li>
     *          <b>resultVariablePrefix：</b>指定保存请求参数和响应结果变量的前缀。可以为这些结果变量设置一个统一的前缀，以便于区分和管理这些变量。如果不设置前缀，变量名默认为该Http任务的id+响应结果名。例如，对于
     *          id为httpServiceTask10的任务，其请求URL将保存为httpServiceTask10RequestUrl
     *      </li>
     *      <li>
     *          <b>saveResponseParametersTransient：</b>若配置为true，则会将响应结果设置为瞬时变量
     *      </li>
     *      <li>
     *          <b>saveResponseVariableAsJson：</b>若配置为true，则响应体会保存为JSON格式的变量，可以方便地在流程实例中保存复杂的数据结构
     *      </li>
     *      <li>
     *          <b>httpActivityBehaviorClass：</b>默认值为org.flowable.engine.impl.bpmn.http.DefaultBpmnHttpActivityDelegate。可以配置为自定义扩展实现类的全路径类名
     *      </li>
     *  </ul>
     *  </p>
     **/
    @Test
    public void testBase() {

    }


    /**
     *  <h2>Http服务设置变量</h2>
     *  <hr/>
     *  <p>
     *      <ul>
     *          <li>
     *              <b>responseProtocol：</b>HTTP版本
     *          </li>
     *          <li>
     *              <b>responseReason：</b>HTTP响应原因短语
     *          </li>
     *          <li>
     *              <b>responseStatusCode：</b>HTTP响应状态码，比如200
     *          </li>
     *          <li>
     *              <b>responseHeaders：</b>行分隔的HTTP响应头。例如：{@code Content-Type: application/json}
     *          </li>
     *          <li>
     *              <b>responseBody：</b>字符串形式的响应体
     *          </li>
     *          <li>
     *              <b>errorMessage：</b>被忽略的异常信息
     *          </li>
     *      </ul>
     *   &emsp;&emsp;默认情况下，当发生链接、IO或其他未处理的异常时，Http任务会抛出FlowableException。同时，默认情况下不会处理任何重定向、客户端或服务端错误状态码。如果需要自定义处理异常
     *  及HTTP状态的方式，可以设置failStatusCodes及handleStatusCodes字段。
     *  </p>
     *  <p>
     *      <h3>handleStatusCodes配置</h3>
     *   &emsp;&emsp;通过设置handleStatusCodes属性，可以配置Http任务在遇到特定状态码时抛出BpmnError异常，与其他BPMN异常一样，需要由对应的错误边界事件处理。示例代码如下：
     *  <pre>
     *      {@code
     *          <serviceTask id="handleStatusCodeTask" name="Http任务" flowable:type="http">
     *              <extensionElements>
     *                  <flowable:field name="requestMethod">
     *                      <flowable:string><![CDATA[GET]]></flowable:string>
     *                  </flowable:field>
     *                  <flowable:field name="requestUrl">
     *                      <flowable:string><![CDATA[http://localhost:8080/api/fail]]></flowable:string>
     *                  </flowable:field>
     *                  <flowable:field name="handleStatusCodes">
     *                      <flowable:string><![CDATA[4XX]]></flowable:string>
     *                  </flowable:field>
     *              </extensionElements>
     *          </serviceTask>
     *          <boundaryEvent id="catch400ErrorBoundaryEvent" attachedToRef="handleStatusCodeTask">
     *              <errorEventDefinition errorRef="HTTP400"/>
     *          </boundaryEvent>
     *      }
     *  </pre>
     *  <h3>failStatusCodes配置</h3>
     *  &emsp;&emsp;通过handleStatusCodes配置了4XX，表示Http任务在遇到4XX状态码时抛出BpmnError。如果状态码为400，则BpmnError的错误码为HTTP400，会被附属的错误边界事件catch400ErrorBoundaryEvent捕获。
     *  此外可以通过设置failStatusCodes字段，可以配置Http任务在遇到特定状态码时抛出异常或者进行重试。示例代码如下：
     *  <pre>
     *      {@code
     *          <serviceTask id="failStatusCodesTask" name="Http服务" flowable:async="true" flowable:type="http">
     *              <extensionElements>
     *                  <flowable:field name="requestMethod">
     *                      <flowable:string><![CDATA[GET]]></flowable:string>
     *                  </flowable:field>
     *                  <flowable:field name="requestUrl">
     *                      <flowable:string><![CDATA[http://localhost:8080/api/fail]]></flowable:string>
     *                  </flowable:field>
     *                  <flowable:field name="failStatusCodes">
     *                      <flowable:string><![CDATA[400,5XX]]></flowable:string>
     *                  </flowable:field>
     *                  <flowable:failedJobRetryTimeCycle>R5/PT3S</flowable:failedJobRetryTimeCycle>
     *             </extensionElements>
     *         </serviceTask>
     *      }
     *  </pre>
     *  &emsp;&emsp;代码配置了failStatusCodes，表示Http任务在遇到“400,5XX”状态码时抛出FlowableException。同时配置Http任务的async属性为true、扩展属性failedJobRetryTimeCycle为R5/PT3S，表示该Http任务为异步执行，
     *  当遇到400及5XX状态码时，将按照3秒的间隔重试5次。
     *  <h3>HttpClientConfig配置</h3>
     *  &emsp;&emsp;Flowable的Http任务基于Apache Http Client访问外部HTTP服务，支持通过可配置的HTTP客户端发出HTTP请求，在工作流引擎配置中配置httpClientConfig属性即可。示例代码如下：
     *  <pre>
     *      {@code
     *          <bean id="processEngineConfiguration" class="org.flowable.engine.impl.cfg.StandaloneProcessEngineConfiguration">
     *              <!-- http客户端配置 -->
     *              <property name="httpClientConfig" ref="httpClientConfig"/>
     *          </bean>
     *          <bean id="httpClientConfig" class="org.flowable.engine.cfg.HttpClientConfig">
     *              <!-- 连接超时时间，单位毫秒。默认值为5000 -->
     *              <property name="connectTimeout" value="5000"/>
     *              <!-- Socket超时时间，单位毫秒。默认值为5000 -->
     *              <property name="socketTimeout" value="5000"/>
     *              <!-- 请求连接超时时间，单位毫秒。默认值为5000 -->
     *              <property name="connectionRequestTimeout" value="5000"/>
     *              <!-- 请求重试次数，默认值为3。配置为0表示不重试 -->
     *              <property name="requestRetryLimit" value="5"/>
     *          </bean>
     *      }
     *  </pre>
     *  </p>
     **/
    @Test
    public void testServer() {

    }

    /**
     *  <h2>流程案例</h2>
     *  <p>
     *      “获取Ip信息”服务任务为Http任务，它会调用 RESTful API查询IP相关信息。<br/>
     *      <b>流程定义文件：</b>{@link flow/httpServiceTaskProcess.bpmn20.xml}<br/>
     *      <b>流程预览图：</b><br/>
     *      <img src="https://cdn4.winhlb.com/2025/05/15/68255e251bf09.png" />
     *  </p>
     **/
    @Test
    public void testExample() {
        /**
         *  当前案例无法使用，待后续返回进行优化。
         **/
        Deployment deploy = repositoryService.createDeployment()
                .name("Http任务节点案例")
                .addClasspathResource("flow/httpServiceTaskProcess.bpmn20.xml")
                .deploy();
        log.info("部署流程定义：{}", deploy.getId());
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        log.info("流程定义编号：{}", procDef.getId());
        //启动流程实例
        ProcessInstance procInst = runtimeService.startProcessInstanceById(procDef.getId());
        TaskQuery taskQuery = taskService.createTaskQuery()
                .processInstanceId(procInst.getId());

        //查询并完成第一个任务
        Task firstTask = taskQuery.singleResult();
        //设置流程变量
        Map<String, Object> varMap = MapUtil.builder(new HashMap<String, Object>())
                .put("ip", "114.247.88.20")
                .build();

        taskService.complete(firstTask.getId(), varMap);

        /**
         *  如果执行时发现报空指针异常时，也就是如下错误：
         *  java.lang.NullPointerException
         * 	    at java.util.concurrent.CompletableFuture.screenExecutor(CompletableFuture.java:415)
         * 	    at java.util.concurrent.CompletableFuture.supplyAsync(CompletableFuture.java:1843)
         * 	    at org.flowable.common.engine.impl.async.DefaultAsyncTaskExecutor.submit(DefaultAsyncTaskExecutor.java:89)
         * 	    at org.flowable.common.engine.impl.async.DefaultAsyncTaskInvoker.submit(DefaultAsyncTaskInvoker.java:40)
         *
         * 	当前案例无法演示
         **/

        //查询历史变量
        List<HistoricVariableInstance> variableList = historyService
                .createHistoricVariableInstanceQuery()
                .processInstanceId(procInst.getId()).list();
        variableList.stream()
                .forEach((variable) ->
                        log.info("流程变量名：{}，变量类型：{}，变量值：{}", variable.getVariableName(),
                        variable.getVariableTypeName(),variable.getValue())
                );

        /**
         *  演示结果：
         **/
    }
}
